import tester.*;
  
// Lecture 5 
// CS U213 Fall 2008
// bookstore-methods4.java
/*
;; A Book is (make-book String Author Num Symbol)
;; There are three kinds of books: fiction, nonfiction, textbook
;; represented by symbols 'F 'N 'T
(define-struct book (title author price kind))

;; An Author is (make-author String Num)
(define-struct author (name yob))

;; Examples of authors
(define eh (make-author "Hemingway" 1900))
(define ebw (make-author "White" 1920))
(define mf (make-author "MF" 1970))

;; Examples of books
(define oms (make-book "Old Man and the Sea" eh 10 'F))
(define eos (make-book "Elements of Style" ebw 20 'N))
(define htdp (make-book "HtDP" mf 60 'T))
*/

/*
 +---------------+
 | Book          |
 +---------------+
 | String title  |
 | Author author |--+
 | int price     |  |
 | char kind     |  |
 +---------------+  |
                    v
             +-------------+
             | Author      |
             +-------------+
             | String name |
             | int yob     |
             +-------------+
*/


//                            
//*****                **     
// *   *                *     
// *   *   ***    ***   * *** 
// ****   *   *  *   *  *  *  
// *   *  *   *  *   *  ***   
// *   *  *   *  *   *  * *   
// *   *  *   *  *   *  *  *  
//*****    ***    ***  ** ****
//                            
//                            

// to represent a book in a bookstore
class Book{
  String title;
  Author author;
  int price;
  char kind;
  
  Book(String title, Author author, int price, char kind){
    this.title = title;
    this.author = author;
    this.price = price;
    this.kind = kind;    
  }

/* TEMPLATE:
   FIELDS:
    ... this.title ...              -- String
    ... this.author ...             -- Author
    ... this.price ...              -- int
    ... this.kind ...               -- char

    METHODS FOR FIELDS:
    ... this.author.sameName(String) ... -- boolean  
  
    METHODS FOR THIS CLASS:
    ... this.writtenBy(String) ...       -- boolean
    ... this.salePrice() ...             -- int
*/

  // was this book written by the given author?
  boolean writtenBy(String authorName){
    return this.author.sameName(authorName);
  }
  
/*  
 the sale price of the book depends on the daily discounts
 these may differ depending on the kind of book
 suppose today we have the following discounts:
 there is 30% discount on fiction books
 there is 20% discount on nonfiction books
 textbooks sell at full price
*/
  
  // compute the discounted sale price for this book
  int salePrice(){    
    if (this.kind == 'F'){
        return this.price - 3 * (this.price / 10);
    } else {if (this.kind == 'N'){
        return this.price - 2 * (this.price / 10);
    } else {
        return this.price;
    }}
  }
}

class Author{
  String name;
  int yob;
  
  Author(String name, int yob){
    this.name = name;
    this.yob = yob;
  }
/* TEMPLATE:
    ... this.name ...                -- String
    ... this.yob ...                 -- Author
  
    ... this.sameName(String) ...    -- boolean
*/
  
  // is this author's name the same as the given name?
  boolean sameName(String name){
    return this.name.equals(name);
  }
}


//                            
//                            
// *****  ***          *****  
//   *     *            *   * 
//   *     *      ***   *   * 
//   *     *     *   *  ****  
//   *     *     *   *  *   * 
//   *     *  *  *   *  *   * 
//   *     *  *  *   *  *   * 
// *****  *****   ***  *****  
//                            
//                            

// to represent a list of books
interface ILoB{
  
  // compute the total sale price of all books in this list
  int totalPrice();

  // produce a list of books sorted by their sale price from this list of books
  ILoB sort();
  
  // produce a list with the given book inserted into this sorted list of books
  // according to the book's sale price
  ILoB insert(Book b);
  
  // is this list of books sorted by the sale price?
  boolean isSorted();

  // is this list of books sorted by the sale price, 
  // with the given book cheaper that all the rest?
  boolean isSortedAcc(Book cheapest);
}

//                                   
//                                   
//*** ***        ***          *****  
// ** **   *      *            *   * 
// ** **  *****   *      ***   *   * 
// * * *   *      *     *   *  ****  
// * * *   *      *     *   *  *   * 
// *   *   *      *  *  *   *  *   * 
// *   *   *   *  *  *  *   *  *   * 
//*** ***   ***  *****   ***  *****  
//                                   
//                                   

// to represent an empty list of books
class MtLoB implements ILoB{
  MtLoB(){}
  
  // compute the total sale price of all books in this list
  public int totalPrice(){
    return 0;
  }

  // produce a list of books sorted by their sale price from this list of books
  public ILoB sort(){
    return this;
  }
  
  // produce a list with the given book inserted into this sorted list of books
  // according to the book's sale price
  public ILoB insert(Book b){
    return new ConsLoB(b, this);
  }

  // is this list of books sorted by the sale price?
  public boolean isSorted(){
    return true;
  }

  // is this list of books sorted by the sale price, 
  // with the given book cheaper that all the rest?
  public boolean isSortedAcc(Book cheapest){
    return true;
  }
}


//                                                 
//                                                 
//  ****                       ***          *****  
// *   *                        *            *   * 
// *       ***  ** **    ****   *      ***   *   * 
// *      *   *  **  *  *   *   *     *   *  ****  
// *      *   *  *   *   ***    *     *   *  *   * 
// *      *   *  *   *      *   *  *  *   *  *   * 
// *   *  *   *  *   *  *   *   *  *  *   *  *   * 
//  ***    ***  *** *** ****   *****   ***  *****  
//                                                 
//                                                 

// to represent a nonempty list of books
class ConsLoB implements ILoB{
  Book first;
  ILoB rest;
  
  ConsLoB(Book first, ILoB rest){
    this.first = first;
    this.rest = rest;
  }
  

/* TEMPLATE:
   FIELDS:
    ... this.first ...              -- Book
    ... this.rest ...               -- ILoB

    METHODS FOR FIELDS:
    ... this.first.writtenBy(String) ...       -- boolean
    ... this.first.salePrice() ...             -- int
  
    ... this.rest.totalPrice() ...             -- int
    ... this.rest.insert(Book) ...             -- ILoB
    ... this.rest.sort() ...                   -- ILoB
    ... this.rest.isSorted() ...               -- boolean
    ... this.rest.isSortedAcc(Book) ...        -- boolean

*/
  // compute the total sale price of all books in this list
  public int totalPrice(){
    return this.first.salePrice() +
           this.rest.totalPrice();
  }
  
  // produce a list of books sorted by their sale price from this list of books
  public ILoB sort(){
    return this.rest.sort().insert(this.first);
  }
  
  // produce a list with the given book inserted into this sorted list of books
  // according to the book's sale price
  public ILoB insert(Book b){
    if (b.salePrice() < this.first.salePrice()){
      return new ConsLoB(b, this);
    }
    else{
      return new ConsLoB(this.first, this.rest.insert(b));
    }
  }

  // is this list of books sorted by the sale price?
  public boolean isSorted(){
    return this.rest.isSortedAcc(this.first);
  }

  // is this list of books sorted by the sale price, 
  // with the given book cheaper that all the rest?
  public boolean isSortedAcc(Book cheapest){
    return cheapest.salePrice() <= this.first.salePrice() &&
           this.rest.isSortedAcc(this.first);
  }
}


//                                                        
//                                                        
//******                               **                 
// *   *                                *                 
// * *   **  **   ***  *** *  ** **     *     ***    **** 
// ***    *  *   *   *  * * *  **  *    *    *   *  *   * 
// * *     **     ****  * * *  *   *    *    *****   ***  
// *       **    *   *  * * *  *   *    *    *          * 
// *   *  *  *   *   *  * * *  *   *    *    *      *   * 
//****** **  **   ************ ****   *****   ****  ****  
//                             *                          
//                            ***                         
//                                                        

// Examples and tests for books and lists of books
class Examples{
  Examples(){}
  
  Author eh = new Author("Hemingway", 1900);
  Author ebw = new Author("White", 1920);
  Author mf = new Author("MF", 1970);

  Book oms = new Book("Old Man and the Sea", this.eh, 10, 'F');
  Book eos = new Book("Elements of Style", this.ebw, 20, 'N');
  Book htdp = new Book("HtDP", this.mf, 60, 'T');
  Book ll = new Book("Little Lisper", this.mf, 30, 'N');
  
  ILoB mtlob = new MtLoB();
  ILoB blist2 = new ConsLoB(this.oms, new ConsLoB(this.eos, this.mtlob));
  ILoB blist3 = new ConsLoB(this.htdp, this.blist2);
  
  ILoB blist3Sorted = new ConsLoB(this.oms,
                      new ConsLoB(this.eos,
                      new ConsLoB(this.htdp, this.mtlob)));

  ILoB blist3Insert = new ConsLoB(this.oms,
                      new ConsLoB(this.eos,
                      new ConsLoB(this.ll,
                      new ConsLoB(this.htdp, this.mtlob))));
  
  
  // test the method salePrice in the class Book
  boolean testSalePrice(Tester t){
    return
    t.checkExpect(this.oms.salePrice(),  7) &&
    t.checkExpect(this.eos.salePrice(),  16) &&
    t.checkExpect(this.htdp.salePrice(),  60) &&
    t.checkExpect(this.ll.salePrice(),  24);}

  // test the method totalPrice in the classes that implement ILoB
  boolean testTotalPrice(Tester t){
    return
    t.checkExpect(this.mtlob.totalPrice(),  0) &&
    t.checkExpect(this.blist2.totalPrice(),  23) &&
    t.checkExpect(this.blist3.totalPrice(),  83);}
    
  // test the method sort in the classes that implement ILoB
  boolean testSort(Tester t){
    return
    t.checkExpect(this.mtlob.sort(),  this.mtlob) &&
    t.checkExpect(this.blist2.sort(),  this.blist2) &&
    t.checkExpect(this.blist3.sort(),  this.blist3Sorted);}

  // test the method insert in the classes that implement ILoB
  boolean testInsert(Tester t){
    return
    t.checkExpect(this.mtlob.insert(this.oms) , 
       new ConsLoB(this.oms, this.mtlob)) &&
    t.checkExpect(this.blist2.insert(this.htdp),  this.blist3Sorted) &&
    t.checkExpect(this.blist3Sorted.insert(this.ll),  this.blist3Insert);}

  // test the method isSorted in the classes that implement ILoB
  boolean testIsSortedAcc(Tester t){
    return
    t.checkExpect(this.mtlob.isSortedAcc(this.htdp),  true) &&
    t.checkExpect((new ConsLoB(this.htdp, this.mtlob)).isSortedAcc(this.eos) , 
        true) &&
    t.checkExpect(this.blist2.isSortedAcc(this.htdp),  false) &&
    t.checkExpect(this.blist2.isSortedAcc(new Book("LS", this.mf, 5, 'N')) , 
        true);}

  // test the method isSorted in the classes that implement ILoB
  boolean testIsSorted(Tester t){
    return
    t.checkExpect(this.mtlob.isSorted(),  true) &&
    t.checkExpect((new ConsLoB(this.htdp, this.mtlob)).isSorted(),  true) &&
    t.checkExpect(this.blist3.isSorted(),  false) &&
    t.checkExpect(this.blist3Sorted.isSorted(),  true);}
  
}
